import math
import random
import logging

from .base import BaseChannel
from .string import *

logger = logging.getLogger('RadioChannel')

class RadioChannel(BaseChannel):
    
    def __init__(self, env, nodes=[]):
        self.env = env
        self.nodes = []
        for n in nodes:
            self.add_node(n)

    def add_node(self, node):
        self.nodes.append(node)

    def add_nodes(self, nodes):
        for n in nodes:
            self.add_node(n)

    def transmit(self, sender, packet, freq, power, metadata):
        sender.last_ts = self.env.now
        for n in self.nodes:
            if n != sender:
                self.env.process(self.deliver_start(n, sender, packet, freq, power, metadata))

    def propagation_loss(self, distance, frequency):
        if distance < 0:
            raise ValueError('Distance must bigger than 0')
        if frequency < 0:
            raise ValueError('Frequency must bigger than 0')
        if distance == 0:
            return 0
        loss = 20 * math.log10(distance) + 20 * math.log10(frequency/1e9) + 32.45
        return round(loss, 2)

    def propagation_delay(self, distance):
        if distance < 0:
            raise ValueError('Distance must bigger than 0')
        delay = distance / 3e8
        return delay
        
    def deliver_start(self, receiver, sender, packet, freq, power, metadata):

        dist = sender.position.distance_to(receiver.position)
        logger.debug(f'{self.env.now:.6f} {sender.id} deliver to {receiver.id}')

        # Delay time
        delay = self.propagation_delay(dist)
        logger.debug(f'{self.env.now:.6f} {sender.id} delay:{elapsed_str(delay)}')
        yield self.env.timeout(delay)

        # Check conflict and send
        current_time = self.env.now
        concurrent = False
        for n in self.nodes:
            # If other linked node is send
            if n != sender:
                if current_time - n.last_ts < 2 *delay:
                    concurrent = True
                    logger.warning(f'{self.env.now:.6f} {sender.id} conflict with {n.id}')
                    break

        # Random collision
        if concurrent and random.random() < self.collision_prob:
            return

        # Propagation Loss
        loss = self.propagation_loss(dist, freq)
        rssi = power - loss
        logger.debug(f'{self.env.now:.6f} {sender.id} loss:{loss:.2f} rssi:{rssi:.2f}')

        receiver.phy.rx(sender, packet, rssi, metadata)
